﻿//------------------------------------------------------------------------------------------------- 
// <copyright file="TypeUtility.cs" company="Christopher Hines">
// Copyright (c) Christopher Hines. All rights reserved.
// </copyright>
// <summary>
//    Responsible for type-specific dynamic handling
// </summary>
//-------------------------------------------------------------------------------------------------

namespace Copperfield
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// Responsible for type-specific dynamic handling
    /// </summary>
    internal static class TypeUtility
    {
        /// <summary>
        /// Returns cached properties for a specified type
        /// </summary>
        /// <param name="type">The type for which cached properties should be returned</param>
        /// <returns>The cached properties for the type</returns>
        internal static List<PropertyInfo> GetCachedProperties(Type type)
        {
            List<PropertyInfo> propertyInfo;

            if (!ReflectionCache.TryGet(type, ReflectionCache.PropertyInfoKey, out propertyInfo))
            {
                propertyInfo = type.GetProperties().ToList();
                ReflectionCache.Set(type, propertyInfo, ReflectionCache.PropertyInfoKey);
            }

            return propertyInfo;
        }

        /// <summary>
        /// Returns context filters for the specified type
        /// </summary>
        /// <param name="type">The type for which context filter attributes should be returned</param>
        /// <returns>The collection of context filter attributes</returns>
        internal static List<ContextFilterAttribute> GetContextFilterAttributes(Type type)
        {
            return ReflectionCache.GetAttributesOfType<ContextFilterAttribute>(type).ToList();
        }

        /// <summary>
        /// Returns a generic type for the arguments, trying to get an instance from the cache first
        /// </summary>
        /// <param name="destinationType">
        /// The destination generic type
        /// </param>
        /// <param name="genericArgumentType">
        /// The type to use as the generic argument for the new type
        /// </param>
        /// <returns>
        /// The new generic type
        /// </returns>
        internal static Type CreateGenericType(Type destinationType, Type genericArgumentType)
        {
            if (!destinationType.IsGenericTypeDefinition)
            {
                throw new ArgumentException("The destination type must be a generic type definition", "destinationType");
            }

            var key = string.Concat("BuildGenericType.", destinationType.FullName, ".", genericArgumentType.FullName);

            Type createdGenericType;
            if (!ReflectionCache.TryGet(genericArgumentType, key, out createdGenericType))
            {
                createdGenericType = destinationType.MakeGenericType(genericArgumentType);
                ReflectionCache.Set(genericArgumentType, createdGenericType, key);
            }

            return createdGenericType;
        }

        /// <summary>
        /// Returns a delegate for generating compatible contexts for a specified type
        /// </summary>
        /// <param name="contextType">The context type</param>
        /// <returns>The compatible contexts delegate</returns>
        internal static Func<Type, IEnumerable<IComparable>> GetRelatedContextsDelegate(Type contextType)
        {
            if (contextType == null)
            {
                throw new ArgumentOutOfRangeException("contextType", "The context type can not be null");
            }

            if (!typeof(IComparable).IsAssignableFrom(contextType))
            {
                throw new ArgumentOutOfRangeException("contextType", "The context type must be a type of IBusinessContext");
            }

            Func<Type, IEnumerable<IComparable>> relatedContextDelegate;
            if (!ReflectionCache.TryGet(contextType, ReflectionCache.RelatedContextsKey, out relatedContextDelegate))
            {
                relatedContextDelegate = CompilerUtility.CompileRelatedContextsDelegate(contextType);

                ReflectionCache.Set(contextType, relatedContextDelegate, ReflectionCache.RelatedContextsKey);
            }

            return relatedContextDelegate;
        }

        /// <summary>
        /// Returns the collection flattener for the specified type
        /// </summary>
        /// <param name="pluginType">
        /// The plugin type
        /// </param>
        /// <returns>
        /// The collection flattener
        /// </returns>
        internal static Func<IEnumerable<object>, object> GetCollectionFlattenerDelegate(Type pluginType)
        {
            Func<IEnumerable<object>, object> collectionFlattenerDelegate;
            if (!ReflectionCache.TryGet(pluginType, ReflectionCache.CollectionFlattenerKey, out collectionFlattenerDelegate))
            {
                collectionFlattenerDelegate = CompilerUtility.CompileConvertToFlatObjectDelegate(pluginType);
                ReflectionCache.Set(pluginType, collectionFlattenerDelegate, ReflectionCache.CollectionFlattenerKey);
            }

            return collectionFlattenerDelegate;
        }

        /// <summary>
        /// Returns the context provider Type for the specified context Type
        /// </summary>
        /// <param name="contextType">The context Type for which the context provider Type should be returned</param>
        /// <returns>The context provider Type for the specified context Type</returns>
        internal static Type GetTypeOfContextManager(Type contextType)
        {
            if (contextType == null)
            {
                throw new ArgumentException("The context type can not be null");
            }

            if (!typeof(IComparable).IsAssignableFrom(contextType))
            {
                throw new ArgumentOutOfRangeException("contextType", "The context type must be a type of IBusinessContext");
            }

            Type genericContextProviderType;
            if (!ReflectionCache.TryGet(contextType, ReflectionCache.ContextProviderKey, out genericContextProviderType))
            {
                var contextProviderType = typeof(ContextManagerBase<>);
                genericContextProviderType = contextProviderType.MakeGenericType(new[] { contextType });

                ReflectionCache.Set(contextType, genericContextProviderType, ReflectionCache.ContextProviderKey);
            }

            return genericContextProviderType;
        }

        /// <summary>
        /// Gets the cached values for the specified enum Type
        /// </summary>
        /// <param name="enumType">The enum Type for which cached values should be returned</param>
        /// <returns>The collection of cached values</returns>
        internal static List<object> GetCachedValues(Type enumType)
        {
            return EnumValuesCache.GetValues(enumType);
        }

        /// <summary>
        /// Attempts to get the specified attribute from the provided type
        /// </summary>
        /// <typeparam name="T">The type of attribtue to retrieve</typeparam>
        /// <param name="type">The type to search for attributes</param>
        /// <param name="attribute">The retrieved attribute</param>
        /// <returns>A value indicating whether a matching attribute was found</returns>
        internal static bool TryGetAttribute<T>(Type type, out T attribute) where T : Attribute
        {
            return ReflectionCache.TryGetAttribute(type, out attribute);
        }
    }
}
